// Package stringUtils
// @File: randomString.go
// @Description: 随机字符串生成
// @Author: YaoRuiQi
// @Date: 2025/8/23 02:55

package stringUtils

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"strings"
)

const (
	Digits       = "0123456789"
	LowerLetters = "abcdefghijklmnopqrstuvwxyz"
	UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	Symbols      = "!@#$%^&*()-_=+[]{}|;:',.<>/?"
)

// RandomCharOptions 用于指定随机字符串中需要包含哪些字符类型
type RandomCharOptions struct {
	UseDigits bool
	UseLower  bool
	UseUpper  bool
	UseSymbol bool
}

// GenerateRandomString
//
//	@Description: 生成随机字符串
//	@param options 选定字符类型
//	@param length 生成长度
//	@return string 生成的随机字符串
func GenerateRandomString(options RandomCharOptions, length int) (string, error) {
	if length <= 0 {
		return "", fmt.Errorf("随机字符串长度必须大于0")
	}

	// 构建字符池
	var charPools []string
	if options.UseDigits {
		charPools = append(charPools, Digits)
	}
	if options.UseLower {
		charPools = append(charPools, LowerLetters)
	}
	if options.UseUpper {
		charPools = append(charPools, UpperLetters)
	}
	if options.UseSymbol {
		charPools = append(charPools, Symbols)
	}

	if len(charPools) == 0 {
		return "", fmt.Errorf("未选择字符类型")
	}
	if length < len(charPools) {
		return "", fmt.Errorf("长度太短，无法包含所有选定的字符类型")
	}

	// 先保证每种类型至少出现一次
	result := make([]byte, 0, length)
	for _, pool := range charPools {
		ch, err := randomChar(pool)
		if err != nil {
			return "", err
		}
		result = append(result, ch)
	}

	// 剩余长度随机填充
	allChars := strings.Join(charPools, "")
	for len(result) < length {
		ch, err := randomChar(allChars)
		if err != nil {
			return "", err
		}
		result = append(result, ch)
	}

	// 打乱顺序
	shuffle(result)

	return string(result), nil
}

// randomChar
//
//	@Description: 随机获取一个字符
//	@param pool 字符池
func randomChar(pool string) (byte, error) {
	n := big.NewInt(int64(len(pool)))
	idx, err := rand.Int(rand.Reader, n)
	if err != nil {
		return 0, err
	}
	return pool[idx.Int64()], nil
}

// shuffle
//
//	@Description: 随机打乱数组 Fisher-Yates 洗牌算法
//	@param data 数组
func shuffle(data []byte) {
	for i := len(data) - 1; i > 0; i-- {
		jBig, _ := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
		j := int(jBig.Int64())
		data[i], data[j] = data[j], data[i]
	}
}
